//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

/***************************************************** 
IMPORTANT NOTE: 
In order to compile and link ribExport.cpp you 
must have Pixar's ri.h header file as well as 
all necessary libraries needed for linking. 
Autodesk cannot distribute these files. 
Please contact Pixar about how to obtain these 
files. 
*****************************************************/ 

#include <maya/MIOStream.h>
#include <math.h>
#include <assert.h>

#if defined (_WIN32)
#include <malloc.h>
#else
#include <alloca.h>
#endif

#if defined (OSMac_)
extern "C" char * strdup (const char *rhs);
#endif
#include <ri.h>

#include <maya/MFnDagNode.h>
#include <maya/MItDag.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnCamera.h>
#include <maya/MFnNurbsCurve.h>
#include <maya/MFnNurbsSurface.h>
#include <maya/MFnTransform.h>
#include <maya/MFnMesh.h>
#include <maya/MDoubleArray.h>
#include <maya/MFloatArray.h>
#include <maya/MIntArray.h>
#include <maya/MPointArray.h>
#include <maya/MFloatVectorArray.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MPoint.h>
#include <maya/MString.h>
#include <maya/MTime.h>
#include <maya/MVector.h>
#include <maya/MFloatVector.h>
#include <maya/MArgList.h>
#include <maya/MColor.h>

#include <maya/MPxFileTranslator.h>
#include <maya/MGlobal.h>
#include <maya/M3dView.h>
#include <maya/MItSurfaceCV.h>
#include <maya/MItMeshPolygon.h>
#include <maya/MItMeshVertex.h>
#include <maya/MDagPath.h>
#include <maya/MDagPathArray.h>
#include <maya/MSelectionList.h>
#include <maya/MAnimControl.h>

#include <maya/MFnLight.h>
#include <maya/MFnNonAmbientLight.h>
#include <maya/MFnNonExtendedLight.h>
#include <maya/MFnAmbientLight.h>
#include <maya/MFnDirectionalLight.h>
#include <maya/MFnLightDataAttribute.h>
#include <maya/MFnPointLight.h>
#include <maya/MFnSpotLight.h>

#include <maya/MFnSet.h>
#include <maya/MFnLambertShader.h>
#include <maya/MFnBlinnShader.h>
#include <maya/MFnPhongShader.h>

////////////////////////
// Macros and Defines //
////////////////////////

// Equivalence test for floats.  Equality tests are dangerous for floating      
// point values 
//
#define FLOAT_EPSILON 0.0001

inline bool equiv( float val1, float val2 )
{
    return ( fabsf( val1 - val2 ) < FLOAT_EPSILON );
}

// Specifies how the start/end frame is set
//
#define USE_GLOBALS      0
#define USE_TIMESLIDER   1

#ifndef  MM_TO_INCH
# define MM_TO_INCH                     0.03937
#endif

///////////
// Enums //
///////////

enum ObjectType {
    MRT_Unknown	     = 0, 
    MRT_Nurbs	     = 1, 
    MRT_Mesh	     = 2, 
    MRT_Light	     = 3, 
    MRT_Weirdo	     = 4
};

enum LightType {
    MRLT_Unknown     = 0, 
    MRLT_Ambient     = 1, 
    MRLT_Distant     = 2, 
    MRLT_Point	     = 3, 
    MRLT_Spot	     = 4
};

enum AnimType {
    MRX_Const		 = 0, 
    MRX_Animated	 = 1, 
    MRX_Incompatible = 2
};
    
    
/////////////////////////////
// Global helper functions //
/////////////////////////////
    
bool isObjectVisible( const MDagPath & path )
//  
//  Description:
//      Check if the given object is visible
//  
{
    MFnDagNode fnDN( path );
    // Check the visibility attribute of the node
    //
    MPlug vPlug = fnDN.findPlug( "visibility" );
    // Also check to see if the node is an intermediate object in
    // a computation.  For example, it could be in the middle of a 
    // chain of deformations.  Intermediate objects are not visible.
    //
    MPlug iPlug = fnDN.findPlug( "intermediateObject" );

    bool visible, intermediate;
    vPlug.getValue( visible );
    iPlug.getValue( intermediate );

    return  visible && !intermediate;
}

bool areObjectAndParentsVisible( const MDagPath & path )
//  
//  Description:
//      Check if this object and all of its parents are visible.  In Maya,
//      visibility is determined by  heirarchy.  So, if one of a node's
//      parents is invisible, then so is the node.
//  
{
    bool result = true;
    MDagPath searchPath( path );
    
    while ( true ) {
        if ( !isObjectVisible( searchPath )  ){
            result = false;
            break;
        }
        if ( searchPath.length() == 1 ) break;
        searchPath.pop();
    }
    return result;
}

  
//////////////////////
// Geometry Classes //
//////////////////////

// Base class for storing maya object data in a RIB compatible form
//
class RibData {
public:
    virtual ~RibData();

    virtual void       write() = 0;
	virtual bool       compare( const RibData & other ) const = 0;
	virtual ObjectType type() const = 0;
};

RibData::~RibData() {}

// Storage for Nurbs Surface data
//
class RibSurfaceData : public RibData {
public: // Methods
    
            RibSurfaceData( MObject surface );
    virtual ~RibSurfaceData();
        
    virtual void       write();
	virtual bool       compare( const RibData & other ) const;
	virtual ObjectType type() const;
    
    bool               hasTrimCurves() const;
    void               writeTrimCurves() const;
    
private: // Data
    bool hasTrims;
        
    RtInt     nu, nv;
	RtInt     uorder, vorder;
    RtFloat * uknot;
    RtFloat * vknot;
    RtFloat   umin, umax,
              vmin, vmax;
    RtFloat * CVs;
    
    // Trim information
    //
    RtInt nloops;
    RtInt * ncurves, * order, * n;
	RtFloat * knot, * minKnot, * maxKnot, * u, * v, * w;
};

RibSurfaceData::RibSurfaceData( MObject surface )
//
//  Description:
//      create a RIB compatible representation of a Maya nurbs surface
//
:   uknot( NULL ), vknot( NULL ), CVs( NULL ), ncurves( NULL ), order( NULL ),
    n( NULL ), knot( NULL ), minKnot( NULL ), maxKnot( NULL ), u( NULL ), v( NULL ), 
    w( NULL ), hasTrims( false )
{
    MStatus status = MS::kSuccess;

    MFnNurbsSurface	nurbs( surface, &status ); 
	if (status != MS::kSuccess)
	{
		// seems like some ill-defined patches are sometimes
		// present in the database (cf: test scene)
		//
        MString error("Empty nurbs object, skipped");
        throw( error );
	}
	
    // Extract the order and number of CVs in the surface keeping
    // in mind that UV order is switched between Renderman and Maya
    //
    uorder = nurbs.degreeV() + 1; // uv order is switched
	vorder = nurbs.degreeU() + 1; // uv order is switched
	nv = nurbs.numCVsInU();       // uv order is switched
	nu = nurbs.numCVsInV();       // uv order is switched
 
	if (nu < 2 || nv < 2) {
		// seems like some ill-defined patches are sometimes
		// present in the database (cf: test scene)
		//
        MString error("Ill-defined nurbs object: ");
        error += nurbs.name();
        throw( error );
	}
	
    // Read the knot information
    //
	MDoubleArray uKnots, vKnots;
	nurbs.getKnotsInU(vKnots); // uv order is switched
	nurbs.getKnotsInV(uKnots); // uv order is switched
	
    double uMin_d, uMax_d, vMin_d, vMax_d;
    nurbs.getKnotDomain(uMin_d, uMax_d, vMin_d, vMax_d);
	umin = (RtFloat)vMin_d; // uv order is switched
	umax = (RtFloat)vMax_d; // uv order is switched
	vmin = (RtFloat)uMin_d; // uv order is switched
	vmax = (RtFloat)uMax_d; // uv order is switched
	
    // Allocate CV and knot storage
    //
    CVs   = (RtFloat*)malloc( sizeof( RtFloat ) * ( nu * nv * 4 ) );
    uknot = (RtFloat*)malloc( sizeof( RtFloat ) * ( uKnots.length() + 2 ) );
    vknot = (RtFloat*)malloc( sizeof( RtFloat ) * ( vKnots.length() + 2 ) );
   
    unsigned k;
	for ( k = 0; k < uKnots.length(); k++ ) uknot[k+1] = (RtFloat)uKnots[k];
    // Maya doesn't store the first and last knots, so we double them up
    // manually
    //
    uknot[0] = uknot[1];
    uknot[k+1] = uknot[k];
    
	for ( k = 0; k < vKnots.length(); k++ ) vknot[k+1] = (RtFloat)vKnots[k]; 
    // Maya doesn't store the first and last knots, so we double them up
    // manually
    //
    vknot[0] = vknot[1];
    vknot[k+1] = vknot[k];    
    
    // Read CV information
    //
    MItSurfaceCV cvs( surface, false );
    RtFloat* cvPtr = CVs;
	while(!cvs.isDone()) {
		while(!cvs.isRowDone()) {
			MPoint pt = cvs.position(MSpace::kObject);
			// Maya points are predivided, whereas RIB ones are not.
			*cvPtr++ = (RtFloat)(pt.x * pt.w);
			*cvPtr++ = (RtFloat)(pt.y * pt.w);
			*cvPtr++ = (RtFloat)(pt.z * pt.w);
			*cvPtr++ = (RtFloat)pt.w;
    		cvs.next();
		}
		cvs.nextRow();
	}
    
    // Store trim information
    //
    if (nurbs.isTrimmedSurface()) {
        hasTrims = true;
        
	    unsigned numRegions, numBoundaries, numEdges, numCurves;
    	unsigned r, b, e, c;
		        
        // Get the number of loops
        //
        numRegions = nurbs.numRegions();
        nloops = 0;
        for ( r = 0; r < numRegions; r++ ) {
            numBoundaries = nurbs.numBoundaries( r );
			nloops += numBoundaries;
		}
      
        MIntArray numCurvesPerLoop, orderArray, numCVsArray;
        MFloatArray knotArray, minArray, maxArray;
        MPointArray cvArray;
        
    	// Get the number of trim curves in each loop and gather curve 
		// information
        //
	    for ( r = 0; r < (unsigned)nloops; r++ ) {
            numBoundaries = nurbs.numBoundaries( r );
            for ( b = 0; b < numBoundaries; b++ ) {
                numCurves = 0;
                numEdges = nurbs.numEdges( r, b );
                for ( e = 0; e < numEdges; e++ ) {
                    MObjectArray curves = nurbs.edge( r, b, e, true );
                    numCurves += curves.length();
                    
                    // Gather extra stats for each curve
                    //
                    for ( c = 0; c < curves.length(); c++ ) {
                        unsigned i;
                        
                        // Read the # of CVs in and the order of each curve
                        //
    					MFnNurbsCurve curveFn(curves[c]);
						orderArray.append( curveFn.degree() + 1 );
						numCVsArray.append( curveFn.numCVs() );
                        
                        // Read the CVs for each curve
                        //
                        MPoint pnt;
                        unsigned last = curveFn.numCVs();
                        for ( i = 0; i < last; ++i ) {
                            curveFn.getCV( i, pnt );

							// Maya points are predivided, whereas RIB 
							// ones are not.
							pnt[0] *= pnt[3];
							pnt[1] *= pnt[3];
							pnt[2] *= pnt[3];

                            cvArray.append( pnt );
                        }
                        
                        // Read the knot array for each curve
                        //
                        MDoubleArray knotsTmpArray;
                        curveFn.getKnots( knotsTmpArray );
                        last = knotsTmpArray.length();
                        knotArray.append( (float)knotsTmpArray[0] );
                        for ( i = 0; i < last; ++i ) {
                            knotArray.append( (float)knotsTmpArray[i] );
                        }
                        knotArray.append( (float)knotsTmpArray[last-1] );
                        
                        // Read the knot domain for each curve
                        //
                        double start, end;
                        curveFn.getKnotDomain( start, end );
                        minArray.append( (float) start );
                        maxArray.append( (float) end );
					}
				}
                numCurvesPerLoop.append( numCurves );
			}
		}
        
        // Store the trim information in RIB format
        //
        ncurves = (RtInt*)malloc( sizeof( RtInt ) * numCurvesPerLoop.length() );
        numCurvesPerLoop.get( (int*)ncurves );
        
        order = (RtInt*)malloc( sizeof( RtInt ) * orderArray.length() );
        orderArray.get( (int*)order );
        
        n = (RtInt*)malloc( sizeof( RtInt ) * numCVsArray.length() );
        numCVsArray.get( (int*)n );
        
        knot = (RtFloat*)malloc( sizeof( RtFloat ) * knotArray.length() );
        knotArray.get( knot );
        
        minKnot = (RtFloat*)malloc( sizeof( RtFloat ) * minArray.length() );
        minArray.get( minKnot );
        
        maxKnot = (RtFloat*)malloc( sizeof( RtFloat ) * maxArray.length() );
        maxArray.get( maxKnot );
        
        unsigned last = cvArray.length();
        u = (RtFloat*)malloc( sizeof( RtFloat ) * last );
        v = (RtFloat*)malloc( sizeof( RtFloat ) * last );
        w = (RtFloat*)malloc( sizeof( RtFloat ) * last );
        for ( unsigned i = 0; i < last; ++i ) {
            u[i] = (RtFloat)( cvArray[i].y * cvArray[i].w ); // u
            v[i] = (RtFloat)( cvArray[i].x * cvArray[i].w ); // v
            w[i] = (RtFloat) cvArray[i].w;                   // w
        }
	}
}
 
RibSurfaceData::~RibSurfaceData()
//
//  Description:
//      class destructor
//
{
    // Free all arrays
    //
    if ( NULL != uknot )   free( uknot );
    if ( NULL != vknot )   free( vknot );
    if ( NULL != CVs )     free( CVs );
    if ( NULL != ncurves ) free( ncurves );
    if ( NULL != order )   free( order );
    if ( NULL != n )       free( n );
    if ( NULL != knot )    free( knot );
    if ( NULL != minKnot ) free( minKnot );
    if ( NULL != maxKnot ) free( maxKnot );
    if ( NULL != u )       free( u );
    if ( NULL != v )       free( v );
    if ( NULL != w )       free( w );
}

void RibSurfaceData::write()
//
//  Description:
//      Write the RIB for this surface
//
{
    RiNuPatch( nu, uorder, uknot, umin, umax, nv, vorder, vknot, vmin, vmax, 
               RI_PW, (RtPointer)CVs, RI_NULL );
}

bool RibSurfaceData::compare( const RibData & otherObj ) const
//
//  Description:
//      Compare this surface to the other for the purpose of determining
//      if it is animated.
//
{	
    if ( otherObj.type() != MRT_Nurbs ) return false;
    const RibSurfaceData & other = (RibSurfaceData&)otherObj;
    
    if ( ( nu != other.nu ) ||
         ( nv != other.nv ) ||
         ( uorder != other.uorder ) ||
         ( vorder != other.vorder ) ||
         !equiv( umin, other.umin ) ||
         !equiv( umax, other.umax ) ||
         !equiv( vmin, other.vmin ) ||
         !equiv( vmax, other.vmax ) ) 
    {
        return false;
    }
    
    // Check Knots
    //
    unsigned i;
    unsigned last = nu + uorder;
    for ( i = 0; i < last; ++i ) {
        if ( !equiv( uknot[i], other.uknot[i] ) ) return false;
    }
    last = nv + vorder;
    for ( i = 0; i < last; ++i ) {
        if ( !equiv( vknot[i], other.vknot[i] ) ) return false;
    }
    
    // Check CVs
    //
    last = nu * nv * 4;
    for ( i = 0; i < last; ++i ) {
        if ( !equiv( CVs[i], other.CVs[i] ) ) return false;
    }
    
    // FUTURE: Check trims as well
    
    return true;
}

ObjectType RibSurfaceData::type() const
//
//  Description:
//      return the geometry type
//
{
 	return MRT_Nurbs;
}

bool RibSurfaceData::hasTrimCurves() const
{
 	return hasTrims;   
}

void RibSurfaceData::writeTrimCurves() const
{
    if ( hasTrims ) {
		RiTrimCurve( nloops, ncurves, order, knot, minKnot, maxKnot, n, u, v, w );
    }
}

// Storage for Mesh data
//
class RibMeshData : public RibData {
public: // Methods
    
            RibMeshData( MObject mesh );
    virtual ~RibMeshData();
        
    virtual void       write();
	virtual bool       compare( const RibData & other ) const;
	virtual ObjectType type() const;
    
private: // Data
    RtInt     npolys;
	RtInt   * nverts;
	RtInt   * verts;
	RtPoint * vertexParam;
	RtPoint * normalParam;
    
    unsigned  totalNumOfVertices;
};

RibMeshData::RibMeshData( MObject mesh )
//
//  Description:
//      create a RIB compatible representation of a Maya polygon mesh
//
:   npolys( 0 ),
    nverts( NULL ),
    verts( NULL ),
    vertexParam( NULL ),
    normalParam( NULL ),
    totalNumOfVertices( 0 )
{
    MFnMesh     fnMesh( mesh );
	
	// To handle the cases where there are multiple normals per
	// vertices (hard-edges) we store a vertex for each normal.
	//
    totalNumOfVertices = fnMesh.numNormals();
	
    npolys = fnMesh.numPolygons();
    
    // Allocate memory for arrays
    //
    vertexParam  = (RtPoint*)malloc( sizeof( RtPoint ) * totalNumOfVertices );
    normalParam  = (RtPoint*)malloc( sizeof( RtPoint ) * totalNumOfVertices );
	nverts       = (RtInt*)  malloc( sizeof( RtInt )   * npolys );
    
    // Use maya array for per polygon vertex lists because we don't
    // know the exact size in advance
    //
    MIntArray perPolyVertices;
 
    // Get per face information  	
	//
    MPoint position;
	unsigned count, index = 0;
    for ( MItMeshPolygon polyIt( mesh ); !polyIt.isDone(); polyIt.next() ) {
        count = polyIt.polygonVertexCount();
        nverts[index] = count;
        
		// Note that we need to reverse the normals for RIB so we
		// get the vertex id's in reverse order
		do {
			count--;
			unsigned normalIndex = polyIt.normalIndex( count );
            perPolyVertices.append( normalIndex );
        	position = polyIt.point( count );
			vertexParam[normalIndex][0] = (RtFloat) position.x;
			vertexParam[normalIndex][1] = (RtFloat) position.y;
			vertexParam[normalIndex][2] = (RtFloat) position.z;	
		} while (count != 0);
        ++index;
    }
    
    verts = (RtInt*)malloc( sizeof( RtInt ) * perPolyVertices.length() );
    perPolyVertices.get( (int*)verts );

	MFloatVectorArray normals;
	fnMesh.getNormals( normals );
 	for ( index = 0; index<totalNumOfVertices; index++ ) {
        normalParam[index][0] = normals[index].x;
        normalParam[index][1] = normals[index].y;
        normalParam[index][2] = normals[index].z;	
	}    
}

RibMeshData::~RibMeshData()
//
//  Description:
//      class destructor
//
{
    // Free all arrays
    //
    if ( NULL != nverts ) free( nverts );
    if ( NULL != verts ) free( verts );
    if ( NULL != vertexParam ) free( vertexParam );
    if ( NULL != normalParam ) free( normalParam );
}

void RibMeshData::write()
//
//  Description:
//      Write the RIB for this mesh
//
{
    // Each loop has one polygon, so we just want an array of 1's of
    // the correct size
    //
    RtInt * nloops = (RtInt*)malloc( sizeof( RtInt ) * npolys );
    for ( int i = 0; i < npolys; ++i ) {
        nloops[i] = 1;
    }
    
    RiPointsGeneralPolygons( npolys, nloops, nverts, verts, 
                             RI_P, (RtPointer)vertexParam, 
            				 RI_N, (RtPointer)normalParam, RI_NULL );
    
    // Free the temporary array of 1's
    //
    free( nloops );
}

bool RibMeshData::compare( const RibData & otherObj ) const
//
//  Description:
//      Compare this mesh to the other for the purpose of determining
//      if its animated
//
{	
    if ( otherObj.type() != MRT_Mesh ) return false;
    const RibMeshData & other = (RibMeshData&)otherObj;
    
    if ( npolys != other.npolys ) return false;
    
    int i;
    int totalPolyVerts = 0;
    for ( i = 0; i < npolys; ++i ) {
        if ( nverts[i] != other.nverts[i] ) return false;
        totalPolyVerts += nverts[i];
    }
    
    for ( i = 0; i < totalPolyVerts; ++i ) {
        if ( verts[i] != other.verts[i] ) return false;
    }
    
    for ( i = 0; i < (int) totalNumOfVertices; ++i ) {
        if ( !equiv( vertexParam[i][0], other.vertexParam[i][0] ) ||
             !equiv( vertexParam[i][1], other.vertexParam[i][1] ) ||
             !equiv( vertexParam[i][2], other.vertexParam[i][2] ) ||
             !equiv( normalParam[i][0], other.normalParam[i][0] ) ||
             !equiv( normalParam[i][1], other.normalParam[i][1] ) ||
             !equiv( normalParam[i][2], other.normalParam[i][2] ) )
     	{
            return false;
        }
    }
    return true;
}

ObjectType RibMeshData::type() const
//
//  Description:
//      return the geometry type
//
{
 	return MRT_Mesh;
}


// Storage for Light data
//
class RibLightData : public RibData {
public: // Methods
    
            RibLightData( const MDagPath & light );
    virtual ~RibLightData();
        
    virtual void       write();
	virtual bool       compare( const RibData & other ) const;
	virtual ObjectType type() const;
    
    RtLightHandle      lightHandle() const;
    
private: // Data
    LightType     lightType;
	RtFloat       color[3];
    RtFloat       intensity, coneAngle, penumbraAngle, dropOff;
    RtPoint       from, to;
    
    RtLightHandle handle;
};

RibLightData::RibLightData( const MDagPath & light )
//
//  Description:
//      create a RIB compatible representation of a Maya light
//
:   handle( NULL )
{
    MFnLight    fnLight( light );
    MColor      colorVal = fnLight.color();
    color[0]  = colorVal.r;
    color[1]  = colorVal.g;
    color[2]  = colorVal.b;
    intensity = fnLight.intensity();
    
    MTransformationMatrix worldMatrix = light.inclusiveMatrix();
    MFnLight lightFn( light );
    
    MVector translation = worldMatrix.translation( MSpace::kWorld );
    MVector direction( 0.0, 0.0, -1.0 ); 
    direction *= worldMatrix.asMatrix();
    MVector toPoint = translation + direction.normal();
    
 	from[0] = (RtFloat)translation.x;
 	from[1] = (RtFloat)translation.y;
 	from[2] = (RtFloat)translation.z;
    to[0]   = (RtFloat)toPoint.x;
    to[1]   = (RtFloat)toPoint.y;
    to[2]   = (RtFloat)toPoint.z;

    if ( light.hasFn(MFn::kAmbientLight)) {
        lightType = MRLT_Ambient;
    } else if ( light.hasFn(MFn::kDirectionalLight)) {
        lightType = MRLT_Distant;
    } else if ( light.hasFn(MFn::kPointLight)) {
        lightType = MRLT_Point;
    } else if ( light.hasFn(MFn::kSpotLight)) {
        MFnSpotLight fnSpotLight( light );
        lightType     = MRLT_Spot;
        coneAngle     = (RtFloat) (fnSpotLight.coneAngle() / 2.0);
        penumbraAngle = (RtFloat) (fnSpotLight.penumbraAngle() / 2.0);
        dropOff       = (RtFloat) fnSpotLight.dropOff();
    }
}

RibLightData::~RibLightData() {}

void RibLightData::write()
//
//  Description:
//      Write the RIB for this light
//
{ 
 	switch ( lightType ) {
        case MRLT_Ambient:
            handle = RiLightSource( "ambientlight", 
                                    "intensity", &intensity,
                                    "lightcolor", color,
                                    RI_NULL );
            break;
        case MRLT_Distant:
            handle = RiLightSource( "distantlight", 
                                    "intensity", &intensity,
                                    "lightcolor", color,
                                    "from", from,
                                    "to", to,
                                    RI_NULL );
            break;
        case MRLT_Point:
            handle = RiLightSource( "pointlight", 
                                    "intensity", &intensity,
                                    "lightcolor", color,
                                    "from", from,
                                    RI_NULL );
            break;
        case MRLT_Spot:
            handle = RiLightSource( "spotlight", 
                                    "intensity", &intensity,
                                    "lightcolor", color,
                                    "from", from,
                                    "to", to,
                                    "coneangle", &coneAngle,
                                    "conedeltaangle", &penumbraAngle,
                                    "beamdistribution", &dropOff,
                                    RI_NULL );
            break;
            
        case MRLT_Unknown:
            break; // handle all cases to avoid compiler warnings
    }
}
    
bool RibLightData::compare( const RibData & otherObj ) const
//
//  Description:
//      Light comparisons are not supported in this version.
//
{
 	return true;  
}

ObjectType RibLightData::type() const
//
//  Description:
//      return the object type
//
{
 	return MRT_Light;
}

RtLightHandle RibLightData::lightHandle() const
{
 	return handle;   
}

///////////////////////////////////////////////////
// Classes for Storing and Writing DAG Node Info //
///////////////////////////////////////////////////

// This class represents an object is the DAG such as a light or geometry
//
class RibObj {
public:
	        RibObj( const MDagPath & );
	virtual ~RibObj();
			   
	AnimType		compareMatrix(const RibObj *, int instance);
	AnimType		compareBody(const RibObj *);

	void		    writeObjectInPrologue();  // Write object to frame header
                                              // and get RIB Id
	void		    writeObject();            // Write geometry directly
	void		    writeInstance();          // Write instance
	
	int		        type;
	int		        written;
	bool		    ignore;
	char **		    lightSources;
    
    MMatrix         matrix( int instance ) const;
    
    void            ref();
    void            unref();
    
    RtObjectHandle  handle() const;
    void            setHandle( RtObjectHandle handle );
    RtLightHandle   lightHandle() const;
    
private:
    MMatrix *       instanceMatrices;  // Matrices for all instances of this
                                       // object
    RtObjectHandle  objectHandle;      // Handle used by RenderMan to refer to
                                       // defined geometry
    int             referenceCount;    // Object's reference count
	RibData *	    data;              // Geometry or light data
};

RibObj::RibObj( const MDagPath &path )
//
//  Description:
//      Create a RIB representation of the given node in the DAG
//
  : referenceCount( 0 ),
    instanceMatrices( NULL ),
    objectHandle( NULL ),
    data( NULL )   
{
	try
	{
		MObject obj = path.node();
    
		written = 0;
		lightSources = NULL;
		MFnDagNode nodeFn( obj );
    
		// Store the matrices for all instances of this node at this time
		// so that they can be used to determine if this node's transformation
		// is animated.  This information is used for doing motion blur.
		//
		MDagPathArray instanceArray;
		nodeFn.getAllPaths( instanceArray );
		unsigned last = instanceArray.length();
		instanceMatrices = new MMatrix[last];
		for ( unsigned i = 0; i < last; i++ ) {
			instanceMatrices[i] = instanceArray[i].inclusiveMatrix();
		}

		// Check to see if the object is hidden.
		// An object is hidden if it or any of it's parents are
		// not visible or are intermediate objects.
		//
		ignore = !areObjectAndParentsVisible( path );
    
		// Store the geometry/light data for this object in RIB format
		//
		if ( obj.hasFn(MFn::kNurbsSurface) ) {
			type = MRT_Nurbs;
			data = new RibSurfaceData( obj );
		} else if ( obj.hasFn(MFn::kMesh) ) {
			type = MRT_Mesh;
			data = new RibMeshData( obj );
		} else if ( obj.hasFn(MFn::kLight)) {
			data = new RibLightData( path );
			type = MRT_Light;
		}
    }
	catch (MString /* errorMessage */ )
	{
		// empty surfaces will throw a message. Just ignore the object.
     	// cerr << "RIB Export: "<<errorMessage << endl;
		ignore = true;
	}
}

RibObj::~RibObj()
//
//  Description: 
//      Class destructor
//  
{
    if (data != NULL) delete data;
    if (instanceMatrices != NULL) delete []instanceMatrices;
}

inline RtObjectHandle RibObj::handle() const
//
//  Description: 
//      return the RenderMan instance handle.  This is used to refer to 
//      RIB data that was previously written in the frame prologue.
//  
{
    return objectHandle;
}

inline void RibObj::setHandle( RtObjectHandle handle )
//
//  Description: 
//      set the RenderMan instance handle 
//  
{
    objectHandle = handle;
}

RtLightHandle RibObj::lightHandle() const
//
//  Description: 
//      return the RenderMan handle handle for this light
//  
{
    assert( type == MRT_Light );
    RtLightHandle lHandle = NULL;
    if ( type == MRT_Light ) {
        RibLightData * light = (RibLightData*)data;
        lHandle = light->lightHandle();
    }
    return lHandle;
}

AnimType RibObj::compareMatrix(const RibObj *o, int instance )
// 
//  Description:
//      compare the two object's world transform matrices.  This method also
//      works with instanced objects.  This comparision is used to determine
//      if motion blurring should be done.
//
{
    return (matrix( instance ) == o->matrix( instance ) ? 
                                  MRX_Const : MRX_Animated);
}

AnimType RibObj::compareBody(const RibObj *o)
// 
//  Description:
//      compare the two object's geometry.  This comparision is used to
//      determine if motion blurring should be done 
//
{
    AnimType cmp = MRX_Const;
    if (data == NULL || o->data == NULL) {
        cmp = MRX_Const;
    } else {
        if ( !data->compare( *(o->data) ) ) {
            cmp = MRX_Animated;
        }
    }
    return cmp;
}

void RibObj::writeObjectInPrologue()
// 
//  Description:
//      write the object out and retain the RIB handle.  We will instanciate it
//      later in the frame body.
//
{
    if ( NULL != data ) {
        if ( MRT_Light == type ) {
        	data->write();
        } else {
        	objectHandle = RiObjectBegin();
        	data->write();
        	RiObjectEnd();
        }
    }
}

void RibObj::writeObject()
// 
//  Description:
//      write the object directly.  We do not get a RIB handle in this case
//
{
    if ( NULL != data ) {
        if ( MRT_Light == type ) {
        	data->write();
        } else {
			if ( type == MRT_Nurbs ) {
            	RibSurfaceData * surfData = (RibSurfaceData*)data;
            	if ( surfData->hasTrimCurves() ) {
            		surfData->writeTrimCurves();
            	}
            }
        	data->write();
        }
    }
}

void RibObj::writeInstance()
// 
//  Description:
//      Write out an instance of this data.  We should have already written
//      the geometry in a global block.
//
{
    if ( NULL != data ) {
        if ( MRT_Light == type ) {
        	data->write();
        } else {
            assert(NULL!=handle());
            if ( type == MRT_Nurbs ) {
                RibSurfaceData * surfData = (RibSurfaceData*)data;
                if ( surfData->hasTrimCurves() ) {
                    surfData->writeTrimCurves();
                }
            }
            RiObjectInstance( handle() );
        }
    }
}

MMatrix RibObj::matrix( int instance ) const
// 
//  Description:
//      return the inclusive matrix for the given instance
//
{
 	assert(instance>=0);
    
    return instanceMatrices[instance];
}

void RibObj::ref()
// 
//  Description:
//      bump reference count up by one
//
{
    referenceCount++; 
}

void RibObj::unref()
// 
//  Description:
//      bump reference count down by one and delete if necessary
//
{
    assert(referenceCount>=0);
    
    referenceCount--;
    if ( referenceCount <= 0 ) {
     	delete this;   
    }
}

// This class represents an instance of a DAG node in the hash table that
// stores all objects in the scene.
//
class RibNode {   
public:
	RibNode( RibNode * instanceOfNode = NULL );
	~RibNode();
			
	void 		    set( MDagPath & );
	void		    shift();
			   
	RibNode *	    next;
	char *		    name;
	
    AnimType        matXForm;
	AnimType        bodyXForm;
	
	RibObj *	    object();
	RibObj *	    nextFrameObject();
    
    MDagPath &      path();
     
	MColor			color;
	MObject			findShadingGroup( const MDagPath& path );
	MObject 		findShader( MObject& group );
	void			getIgnoredLights( MObject& group, MObjectArray& lights );
	bool			getColor( MObject& shader, MColor& color );
		   
private:
        
    MDagPath        DagPath;
	RibObj *	   objects[2];
    RibNode *      instance;
};


RibNode::RibNode( RibNode * instanceOfNode )
// 
//  Description:
//      construct a new hash table entry
//
:   next( NULL ),
    name( NULL ),
    matXForm( MRX_Const ),
    bodyXForm( MRX_Const ),
    instance( instanceOfNode )
{
    objects[0] = objects[1] = NULL;
}

RibNode::~RibNode()
// 
//  Description:
//      class destructor
//
{
    if (objects[0] != NULL) objects[0]->unref();
    if (objects[1] != NULL) objects[1]->unref();
    if (name) free(name);
}

void RibNode::shift()
// 
//  Description:
//      Each node stores its object at the current frame and at the following
//      frame in order to do motion blurring.  This method shifts the 
//      stored object for the next frame over to be the object for the current
//      frame.  This method typically gets called when the time is advanced
//      by one frame.
//
{
    assert(instance==NULL);
    if (bodyXForm == MRX_Const && objects[0]->written) {
        objects[1]->written = 1;
    }
    if (objects[0] != NULL) {
         objects[0]->unref();
     }
    objects[0] = objects[1];
    objects[1] = NULL;
}

inline RibObj * RibNode::object()
// 
//  Description:
//      get the object (surface, mesh, light, etc) refered to by this node
//
{
    if ( NULL != instance ) {
     	// We are instanced to another RibNode, so use their object instead
        //
        return instance->object();
    } else {
        return objects[0];
    }
}

inline RibObj * RibNode::nextFrameObject() 
// 
//  Description:
//      get the object (surface, mesh, light, etc) refered to by this node
//      as it will be in the next frame.  This is used for motion blur 
//      purposes.
//
{
    if ( NULL != instance ) {
     	// We are instanced to another RibNode, so use their object instead
        //
        return instance->nextFrameObject();
    } else {
        return objects[1];
    }
}
 
void RibNode::set( MDagPath &path )
// 
//  Description:
//      set this node with the given path.  If this node already refers to
//      given object, then it is assumed that the path represents the object
//      at the next frame.
//
{
    DagPath = path;
    int instanceNum = path.instanceNumber();
   
	// Get the object's color
	//	  
 	MObject group = findShadingGroup( path );
	MObject shader = findShader( group );
	if ( !getColor( shader, color ) ) {
		// This is how we specify that the color was not found.
		//
		color.r = -1.0;
	}
	
    if ( NULL != instance ) {
        // We are an instance of another RibNode, we really only need to 
        // compare matrices.  The other node will do all of the work
        //
		if ( nextFrameObject() != NULL ) {
			matXForm = object()->compareMatrix( nextFrameObject(), instanceNum );
        }
		bodyXForm = instance->bodyXForm;
        name = strdup( instance->name );
    } else {
        // Create a new RIB object for the given path
        //
        RibObj * no = new RibObj( path );
        no->ref();
 
        if (name == NULL) {
            MFnDagNode  fnDagNode( path );
            MObject     parentNode = fnDagNode.parent(0);
            MFnDagNode  fnParentNode(parentNode);
            MStatus     returnStatus;
 
            MString nodeName = fnParentNode.name(&returnStatus);
 
            name = strdup(nodeName.asChar());
        }
        if (objects[0] == NULL) {
            objects[0] = no;
        } else {
            // We have the object data for the current frame, assume that
            // the new object is for the next frame
            //
            if (objects[1] != NULL) {
                if ( MRX_Const == bodyXForm ) {
                    // We can re-use any instance we already wrote into the
                    // prologue
                    //
                    objects[1]->setHandle( objects[0]->handle() );
                }
                shift();
            }
            objects[1] = no;
            matXForm = objects[0]->compareMatrix(objects[1], instanceNum );
            bodyXForm = objects[0]->compareBody(objects[1]);
        }
    }
}

MDagPath & RibNode::path()
//
//  Description:
//      Return the path in the DAG to the instance that this node represents
//
{
 	return DagPath;   
}

MObject RibNode::findShadingGroup( const MDagPath& path )
//
//  Description:
//      Find the shading group assigned to the given object
//
{
	MSelectionList objects;
	objects.add( path );
	MObjectArray setArray;
    
    // Get all of the sets that this object belongs to
    //
	MGlobal::getAssociatedSets( objects, setArray );
	MObject mobj;	
	
    // Look for a set that is a "shading group"
    //
	int last = setArray.length();
	for ( int i=0; i<last; i++ ) 
	{
		mobj = setArray[i];
		MFnSet fnSet( mobj ); 
		MStatus stat;
		if ( MFnSet::kRenderableOnly == fnSet.restriction(&stat) )
		{
			return mobj;		
		}
	}

	return MObject::kNullObj;
}

MObject RibNode::findShader( MObject& group )
//
//  Description:
//      Find the shading node for the given shading group
//
{
	MFnDependencyNode fnNode( group );
	MPlug shaderPlug = fnNode.findPlug( "surfaceShader" );
			
	if ( !shaderPlug.isNull() ) {			
		MPlugArray connectedPlugs;
		bool asSrc = false;
		bool asDst = true;
		shaderPlug.connectedTo( connectedPlugs, asDst, asSrc );

		if ( connectedPlugs.length() != 1 ) {
			cerr << "RIB Export: Error getting shader\n";
		}
		else 
			return connectedPlugs[0].node();
	}			
	
	return MObject::kNullObj;
}

void RibNode::getIgnoredLights( MObject& group, MObjectArray& ignoredLights )
//
//  Description:
//      Get the list of all ignored lights for the given shading group
//
{
	MFnDependencyNode fnNode( group );
	MPlug ilPlug = fnNode.findPlug( "ignoredLights" );
			
	if ( !ilPlug.isNull() ) {		
		MPlugArray connectedPlugs;
		bool asSrc = false;
		bool asDst = true;
		
		// The ignoredLights attribute is an array so this should
		// never happen
		//
		if ( !ilPlug.isArray() )
			return;
		
		for ( unsigned i=0; i<ilPlug.numConnectedElements(); i++ )
		{
			MPlug elemPlug = ilPlug.elementByPhysicalIndex( i );
			connectedPlugs.clear();
			elemPlug.connectedTo( connectedPlugs, asDst, asSrc );
			
			// Since elemPlug is a destination there should
			// only be 1 connection to it
			//
			ignoredLights.append( connectedPlugs[0].node() );
		}
	}			
}

bool RibNode::getColor( MObject& shader, MColor& color )
//
//  Description:
//      Get the color of the given shading node.
//
{
	switch ( shader.apiType() )
	{ 
		case MFn::kLambert :
		{			
			MFnLambertShader fnShader( shader );
			color = fnShader.color();
			break;
		}
		case MFn::kBlinn :
		{
			MFnBlinnShader fnShader( shader );
			color = fnShader.color();
			break;
		}
		case MFn::kPhong :
		{
			MFnPhongShader fnShader( shader );
			color = fnShader.color();
			break;	
		}
		default:
		{
			MFnDependencyNode fnNode( shader );
			MPlug colorPlug = fnNode.findPlug( "outColor" );
			if ( colorPlug.numElements() < 3 ) return false;
			colorPlug[0].getValue( color.r );
			colorPlug[1].getValue( color.g );
			colorPlug[2].getValue( color.b );
			return false;			 	
		}
	}
	return true;
}	

// Hash table for storing information about DAG node instances
//
class RibHT {
    
public:
	RibHT();
	~RibHT();
			   
	int		        insert( MDagPath &, int);

	RibNode*	    find( const MDagPath & );
	RibNode*	    find( const MObject & );
	
private:
	RibNode **	    table;
	uint		    hash(const char *);
	
	friend class RibItHT;
};

// Size of the hash table.  This absolutely must be a power of 2!
static const uint MR_HASHSIZE = 16384;

RibHT::RibHT()
//
//  Description:
//      Class constructor.
//
{
    table = (RibNode **)calloc(MR_HASHSIZE, sizeof(RibNode *));
}

RibHT::~RibHT()
//
//  Description:
//      Class destructor.
//
{
    for (int i=0; i<MR_HASHSIZE; i++) if (table[i]) {
		RibNode * node = table[i];
		do {
			RibNode * nextNode = node->next;
			delete node;
			node = nextNode;
		} while(node);
    }
	
	if ( table != NULL )
		free( table );
}

uint RibHT::hash(const char *str)
//
//  Description:
//      hash function for strings
//
{
#if defined (OSMac_)
	unsigned long hc = 0;
#else
    ulong hc = 0;
#endif    
    while(*str) {
		hc = hc * 13 + *str * 27;   // change this to a better hash func
		str++;
    }
	
    return (uint)( hc & (MR_HASHSIZE-1) );
}

int RibHT::insert( MDagPath &path, int frame)
//
//  Description:
//      insert a new node into the hash table.
//
{
    MFnDagNode  fnDagNode( path );
    MStatus	returnStatus;
    
    MString nodeName = fnDagNode.name(&returnStatus);
	
    const char * name = nodeName.asChar();
    
    uint	    hc = hash(name);
    RibNode *    node = table[hc];
    RibNode *    newNode = NULL;
    RibNode *    instance = NULL;
    
    // If "node" is non-null then there's already a hash table entry at
    // this point
    //
    RibNode * tail = NULL;
    if ( NULL != node ) {
        while(node) {
            tail = node;
	    	if ( path == node->path() ) break;
            if ( path.node() == node->path().node() ) {
             	// We have found another instance of the object we are object
                // looking for
                //
                instance = node;
            }
	    	node = node->next;
        }
        if ( ( NULL == node ) && ( NULL != instance ) ) {
         	// We have not found a node with a matching path, but we have found
            // one with a matching object, so we need to insert a new instance
            //
            newNode = new RibNode( instance );
        }
    }
    if ( NULL == newNode ) {
        // We have to make a new node
        //
        if (node == NULL) {
	    	node = new RibNode();
        	if ( NULL != tail ) {
                assert(NULL==tail->next);
                tail->next = node;
            } else {
                table[hc] = node;
            }
        }
    } else {
        assert(NULL==node);
        // Append new instance node onto tail of linked list
        //
        node = newNode;
        if ( NULL != tail ) {
            assert(NULL==tail->next);
            tail->next = node;
        } else {
         	table[hc] = node;
        }
    }
	node->set( path );
    return 0;
}

RibNode* RibHT::find( const MDagPath& path )
//
//  Description:
//      find the hash table entry for the given path
//
{
    RibNode * result = NULL;
	MFnDagNode fnDagNode( path );
	MString nodeName = fnDagNode.name();	
	
	const char * name = nodeName.asChar();
    
    uint	     hc = hash(name);
    RibNode *    node = table[hc];

    // If "node" is non-null then there's at least one hash table entry at
    // this point
    //
    if ( NULL != node ) {
        // find the element with the matching dag path
        //
        while ( NULL != node ) {
         	if ( node->path() == path ) {
             	result = node; 
                break;
            }
            node = node->next;
        }
	}
    
    return result;
}

RibNode* RibHT::find( const MObject& object )
//
//  Description:
//      find the hash table entry for the given object
//
{
	MFnDagNode fnDagNode( object );
	MString nodeName = fnDagNode.name();	
	
	const char * name = nodeName.asChar();
    
    uint	     hc = hash(name);
    return table[hc];
}

// Hash Table iterator class
//
class RibItHT {
public:
	RibItHT(RibHT *);
			   
	void		reset();
	bool		isDone()	{ return done; }
	RibNode *	next();
	
private:
    
	RibHT *	    ht;
	bool		done;
	int		    curIndex;
	RibNode *	current;

};

RibItHT::RibItHT(RibHT *iht)
//
//  Description:
//      Class constructor
//
{
    ht = iht;
    reset();
}

void RibItHT::reset()
//
//  Description:
//      Reset to the beginning of the hash table
//
{
    for (curIndex=0; curIndex<MR_HASHSIZE; ++curIndex) {
		current = ht->table[curIndex];
		if (current != NULL) break;
    }
    done = (current == NULL);
}

RibNode *RibItHT::next()
//
//  Description:
//      Advance to the next entry in the table and return a pointer to it.
//
{
    RibNode * rn = current;
    if (current) {
		if (current->next) current = current->next;
		else {
			current = NULL;
			for (++curIndex; curIndex<MR_HASHSIZE; ++curIndex) {
				current = ht->table[curIndex];
				if (current != NULL) break;
			}
			done = (current == NULL);
		}
    }
    return rn;
}


////////////////////////////////
// Maya File Translator Class //
////////////////////////////////

class RibTranslator : public MPxFileTranslator {
public:
	RibTranslator();
	~RibTranslator();

	static void *	    creator();
			    
	virtual MStatus	    writer(const MFileObject &, 
							   const MString &optionsString,
							   MPxFileTranslator::FileAccessMode mode);
	
	virtual bool	    haveReadMethod() const	{ return FALSE; }
	virtual bool	    haveWriteMethod() const	{ return TRUE; }
	
	virtual MFileKind   identifyFile(const MFileObject &,
									 const char *, short) const;
	MString             defaultExtension() const;

private: // Methods  
	
	MStatus		scanScene(int);

	MStatus		getRenderGlobals();
	
	void 		portFieldOfView(		int width, int height,
					 					double& horizontal,
					 					double& vertical,
					 					MFnCamera& fnCamera );
										
	void 		computeViewingFrustum (	double window_aspect,
        								double& left,
        								double& right,
        								double& bottom,
        								double& top,
					    				MFnCamera& cam );

	void		getCameraInfo( MFnCamera& cam );		
		
	MStatus		ribPrologue();
	MStatus		ribEpilogue();

	MStatus		framePrologue(uint);
	MStatus		frameBody();
	MStatus		frameEpilogue(uint);

    void        doAttributeBlocking( const MDagPath & newPath,  
                                     const MDagPath & previousPath );

private: // Data

	enum MRibStatus {
	    kRibOK,
	    kRibBegin, 
	    kRibFrame, 
	    kRibWorld, 
	    kRibError
	};

	MRibStatus	  ribStatus;
	
    // Render Globals and RIB Export Options
    //
	MDagPathArray renderableCameraArray;
	MDagPath	  activeCamera;
	
	int			  frameFirst;
	int			  frameLast;
	uint		  frameBy;
	uint		  width, height, depth;
	int			  outPadding;
  
	bool		  doMotion;           // Motion blur for transformations
	int			  doDef;              // Motion blur for deforming objects
	int			  doCameraMotion;     // Motion blur for moving cameras
	int			  doSingleFile;       // Write all frames to a single file
	int			  doExtensionPadding;
	int			  pixelSamples;
    
    bool          renderAllCameras;   // Render all cameras, or only active ones
	
	bool		  ignoreFilmGate;
	double		  fov_ratio;
	int			  cam_width, cam_height;
    
	static MString magic;
	
    // Data used to construct output file names
    //
	MString 	  outFormat;
	MString 	  outFormatString;
	MString 	  outExt;
	int 		  outFormatControl;
	bool		  animation;
	bool		  useFrameExt;
	MString 	  animExt;
	MString		  baseFileName;
	MString		  extension;
	MString 	  imageName;
	
    // Structure for camera data
    //
#ifndef _WIN32
	struct {
#else
	struct structCamera {
#endif	    
		MMatrix	mat;
	    double	neardb, fardb;
	    double	hFOV;
	    int		isOrtho;
	    char *  name;
	    bool	motionBlur;
	    double	shutter;
        MString imageMode;
	} camera[2];
	
    // Hash table for scene
    //
	RibHT		*htable;
    
    // Depth in attribute blocking
    //
    int         attributeDepth;
    
};

const char *const ribOptionScript = "ribExportOptions";
const char *const ribDefaultOptions = 
	"singleFile=1;"
	"cameraMotion=1;"
	"matrixMotion=1;"
	"geometryMotion=1;"
	"pixelSamples=3;"
	"extensionPadding=0;"
	;

// Initialize the magic string by which we identify RIB file
//
MString RibTranslator::magic("##RenderMan");

void *RibTranslator::creator()
//
//  Description:
//      Create a new instance of the translator
//
{
    return new RibTranslator();
}

RibTranslator::RibTranslator()
//
//  Description:
//      Class constructor
//
{
    doMotion = false;		// matrix motion blocks
    doDef = 0;				// geometry motion blocks
    doCameraMotion = 0;		// camera motion blocks
    doSingleFile = 1;		// all frames in one file or splitted
	doExtensionPadding = 0; // pad the frame number in the rib file names
    frameFirst = 1;			// range
    frameLast = 1;
    frameBy = 1;
    outPadding = 4;
    pixelSamples = 3;
	depth = 1;
	outFormatControl = 1;
    outFormat = "tiff";
	animation = false;
	useFrameExt = true;		// Use frame extensions
    outExt = "tif";
	animExt = ".%0*d";
    
    camera[0].name = camera[1].name = NULL;
}   

RibTranslator::~RibTranslator()
//
//  Description:
//      Class destructor
//
{
    if (camera[0].name) free(camera[0].name);
    if (camera[1].name) free(camera[1].name);
}   

void RibTranslatorErrorHandler( RtInt code, RtInt severity, char * message )
//
//  Description:
//      Error handling function.  This gets called when the RIB library
//      detects an error.
//
{
    MString error( message );
    throw error;
}

MStatus RibTranslator::writer( const MFileObject &fileObject, 
							   const MString &optionsString,
							   MPxFileTranslator::FileAccessMode mode )
//
//  Description:
//      This method actually does the file export 
//
{
    // Register the error handler, which throws exceptions
    //
    RiErrorHandler( RibTranslatorErrorHandler );
    
    try {

		// Separate the base file name from the ".rib" extension (if
		// one exists).
        const MString fileName = fileObject.fullName();
		extension.set(".rib");
		int extLocation = fileObject.name().rindex('.');
		
		if ( 0 != extLocation && fileObject.name().substring(extLocation,
								 fileObject.name().length()-1) == extension ) {
			baseFileName = fileObject.name().substring(0, extLocation-1);
		} else {
			baseFileName = fileObject.name();	
			extension.clear();
		}
 
		// If an options string was provided, parse it.
        if ( optionsString.length() > 0 ) {
            MStringArray optionList;
            MStringArray theOption;
            optionsString.split(';', optionList);
 
            for(unsigned i=0; i<optionList.length(); ++i) {
                theOption.clear();
                optionList[i].split('=', theOption);
                if (theOption[0] == MString("geometryMotion"))
                    doDef = theOption[1].asInt();
                else if (theOption[0] == MString("singleFile"))
                    doSingleFile = theOption[1].asInt();
                else if (theOption[0] == MString("extensionPadding"))
                    doExtensionPadding = theOption[1].asInt();
                else if (theOption[0] == MString("pixelSamples"))
                    pixelSamples = theOption[1].asInt();
            }
        }
 
        // Get render globals info
        // i.e. resolution, matrix motion blur, frame range
        //
        getRenderGlobals();

	    // Remember the frame the scene was at so we can restore it later.
	    MTime currentFrame = MAnimControl::currentTime();

        if (doSingleFile) {
            // Write all the the frames into a single file
            //
            RiBegin( (RtToken)fileName.asChar() );
            
            htable = new RibHT();
            if ( ribPrologue() == MS::kSuccess ) {
               uint numCameras = renderableCameraArray.length();
			   for ( uint c=0; c<numCameras; c++ ) {
					activeCamera = renderableCameraArray[c];
					
					// Extract the string to be used in the fileName for
					// this image
					MFnCamera fnCamera(activeCamera);
					fnCamera.findPlug("imageName").getValue(imageName);		   
			  
			  		getCameraInfo( fnCamera );
			   
				    scanScene( frameFirst );
	                for( int frame = frameFirst; frame <= frameLast; frame++ ) {
	                    if ( doMotion || doDef || frame != frameLast ) {
	                        scanScene( frame + 1 );
	                    } else if ( frameFirst != frameLast ) {
	                        scanScene( frame );
	                    }
	                    if (   framePrologue( frame ) != MS::kSuccess
	                        || frameBody()            != MS::kSuccess
	                        || frameEpilogue( frame ) != MS::kSuccess ) break;
               		}
			    }
                ribEpilogue();
            }
            delete htable;
            htable = NULL; 
            
            RiEnd();
        } else {
		    // Output filenames of the form "baseName[.###][.rib]"
			//
    		MString fileNameFmtString = fileObject.path();
			
			fileNameFmtString += baseFileName;
			fileNameFmtString += animExt;
			fileNameFmtString += extension;

			size_t fileNameLength = fileNameFmtString.length() + 1;		 

			fileNameLength += 10; // Enough to hold the digits for 1 billion
								  // frames.

	        char *frameFileName = (char *)malloc(fileNameLength);

            htable = new RibHT();
			scanScene(frameFirst);
			for( int f=frameFirst; f<=frameLast; f+=frameBy) {
			    sprintf(frameFileName,
						fileNameFmtString.asChar(),
						doExtensionPadding ? outPadding : 0,
						f);

                RiBegin( frameFileName );
                ribStatus = kRibOK;
				
				if ( ribPrologue() == MS::kSuccess) {	
					uint numCameras = renderableCameraArray.length();
					for (uint c=0; c<numCameras; c++) {
						activeCamera = renderableCameraArray[c];
						// Extract the string to be used in the fileName for
						// this image.
						MFnCamera fnCamera(activeCamera);
						fnCamera.findPlug("imageName").getValue(imageName);		
 
				 	   if (doMotion || doDef || ((f+(int)frameBy) <= frameLast)) {
							scanScene(f+frameBy);
	               		} else if (frameFirst != frameLast) {
	               		    scanScene(f);
        	        	}
			
            	    	if (framePrologue(f) != MS::kSuccess
                    			|| frameBody() != MS::kSuccess
                    			|| frameEpilogue(f) != MS::kSuccess ) {
                    		ribStatus = kRibError;
						}
					}
					ribEpilogue();
				
                	RibItHT iht(htable);

                	while(!iht.isDone()) {
                	    RibNode *   rn = iht.next();
                	    rn->object()->written          = 0;
                        if ( RibObj * rb= rn->nextFrameObject()) 
								rb->written = 0;
                	}
            	}
            	RiEnd();

            	if (ribStatus != kRibOK) break;
            }
	        if (htable)
	            delete htable;

            free(frameFileName);
        }

	    // Return to the frame we were at before we ran the animation
	    MGlobal::viewFrame (currentFrame);

        return (ribStatus == kRibOK ? MS::kSuccess : MS::kFailure);
    } catch ( MString errorMessage ) {
     	cerr << "RIB Export: "<< errorMessage << endl;
        if ( NULL != htable ) delete htable;
    } catch ( ... ) {
        cerr << "RIB Export: Unknown exception thrown\n" << endl;
        if ( NULL != htable ) delete htable;
    }
    return MS::kFailure;
}

MPxFileTranslator::MFileKind RibTranslator::identifyFile(
										const MFileObject &fileName,
										const char *buffer,
										short size) const
//
//  Description:
//      Check the given file to see if it is Renderman data
//
{
	// Check the buffer for the magic "##Renderman" string.
	//
	MFileKind rval;

	if ((size >= (short)magic.length()) &&
		(0 == strncmp(buffer, magic.asChar(), magic.length()))) {
		rval = kIsMyFileType;
	} else
		rval = kNotMyFileType;

	return rval;

}

MString RibTranslator::defaultExtension() const
//
//  Description:
//      Return the default extension for Renderman files
//
{
    return "rib";
}

MStatus initializePlugin(MObject obj)
//
//  Description:
//      Register the exporter when the plug-in is loaded
//
{
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");
    MStatus stat;
    
    stat = plugin.registerFileTranslator("RIBexport", "", 
										 RibTranslator::creator,
										 (char *)ribOptionScript, 
										 (char *)ribDefaultOptions);
    if (!stat) {
		return MS::kFailure;
    }
    return MS::kSuccess;
}

MStatus uninitializePlugin(MObject obj)
//
//  Description:
//      Deregister the exporter when the plug-in is deloaded
//
{
    MFnPlugin plugin(obj);
    return plugin.deregisterFileTranslator("RIBexport");
}

MStatus RibTranslator::getRenderGlobals()
//
//  Description:
//      Get render globals information
//
{
    MStatus status;

    // Get the render globals node
    //
    MSelectionList renderGlobalsList;
    renderGlobalsList.add( "defaultRenderGlobals" );
    MObject renderGlobalsNode;
    if ( renderGlobalsList.length() > 0 ) {

		renderGlobalsList.getDependNode( 0, renderGlobalsNode );
		MFnDependencyNode fnRenderGlobals( renderGlobalsNode );

		// Find the resolution node and get the width and height
		// 
		MPlugArray connectedPlugs;
		MPlug resPlug = fnRenderGlobals.findPlug( "resolution" );
		resPlug.connectedTo( connectedPlugs,
							 true,  // asDestination
							 false, // asSource
							 &status );

		// Must be length 1 or we would have fan-in
		//
		if ( status && connectedPlugs.length() == 1 ) {
			MObject resNode = connectedPlugs[0].node( &status );
			if ( status ) {
				MFnDependencyNode fnRes( resNode );
				MPlug resWidth = fnRes.findPlug( "width" );
				MPlug resHeight = fnRes.findPlug( "height" );
				short res_width, res_height;
				resWidth.getValue( res_width );
				resHeight.getValue( res_height );
				width = (uint)res_width;
				height = (uint)res_height;
			}
		}

		// Check for ouput image format
	
		enum {
			kGIF	= 0, 
			kSoftimage	= 1, 
			kRLA	= 2, 
			kTiff	= 3, 
			kTiff16	= 4, 
			kSGI	= 5, 
			kAlias	= 6, 
			kMaya	= 7, 
			kJPEG	= 8, 
			kEPS	= 9, 
			kMaya16	= 10, 
			kCineon	= 11
		};
	
		MPlug outputFormatPlug = fnRenderGlobals.findPlug( "imageFormat" );
		int outputId;
		outputFormatPlug.getValue( outputId );
		// fprintf(stderr, "outputFormat = %d\n", outputId);
		if (outputId == kSGI) {
			outFormat = "sgif";
			outExt = "sgi";
		} else if (outputId == kAlias) {
			outFormat = "alias";
			outExt = "als";
		} else if (outputId ==kEPS) {
			outFormat = "cpostscript";
			outExt = "ps";
		} else if (outputId ==kCineon) {
			outFormat = "cineon";
			outExt = "cin";
		} else {
			outFormat = "tiff";
			outExt = "tif";
		}
		
		// Whether "outExt" is to be included in file name
		fnRenderGlobals.findPlug("outFormatControl").getValue(outFormatControl);
		
		if (outFormatControl ==  2) {
			fnRenderGlobals.findPlug("outFormatExt").getValue(outFormatString);
		}
		
		// Check if there is animation
		//
		MPlug animPlug = fnRenderGlobals.findPlug( "animation" );
		animPlug.getValue( animation );

		MTime startFrame;
		MTime endFrame;
		float byFrame;

		if ( animation ) {
	
			// Check if the time-slider or renderGlobals is used for 
			// the frame range
			//
			MPlug animPlugVal = fnRenderGlobals.findPlug( "animationRange" );
			short animRange;
			animPlugVal.getValue( animRange );

			if ( USE_GLOBALS == animRange ) {
				fnRenderGlobals.findPlug( "startFrame" ).getValue(startFrame);
				fnRenderGlobals.findPlug( "endFrame" ).getValue(endFrame);
				fnRenderGlobals.findPlug( "byFrameStep" ).getValue(byFrame);

				frameFirst = (int) startFrame.as( MTime::uiUnit() );
				frameLast =  (int) endFrame.as( MTime::uiUnit() );
				frameBy =    (uint) byFrame;
			}
			else {  // USE_TIMESLIDER
				startFrame = MAnimControl::minTime();
				endFrame = MAnimControl::maxTime();

				frameFirst = (int) startFrame.as( MTime::uiUnit() );
				frameLast = (int) endFrame.as( MTime::uiUnit() );
			}

			// Check if motionBlur is turned on.
			// (This is matrix motion blur)
			//
			MPlug mbPlug = fnRenderGlobals.findPlug( "motionBlur" );
			mbPlug.getValue( doMotion );

		}
		else { // No animation
			frameFirst = (int) MAnimControl::currentTime().as( MTime::uiUnit() );
			frameLast = (int) MAnimControl::currentTime().as( MTime::uiUnit() );
			frameBy = 1;
			doMotion = false;
			
			// Whether the frame number is to be incded in the file name
			fnRenderGlobals.findPlug( "useFrameExt" ).getValue(useFrameExt);
		}
		
		fnRenderGlobals.findPlug( "extensionPadding" ).getValue(outPadding);
		if (outPadding > 99)
			outPadding = 99;
			
		// If a film gate is used this tells us whether the image is
		// blacked out in regions outside the film-fit region.
		// In our case we crop the image to the size of the region.
		//		
		MPlug filmGatePlug = fnRenderGlobals.findPlug( "ignoreFilmGate" );
		filmGatePlug.getValue( ignoreFilmGate );
			
        // Check if we should render all cameras, or only the active one
        //
        MPlug raPlug = fnRenderGlobals.findPlug( "renderAll" );
        raPlug.getValue( renderAllCameras );
    }
    else {
		width = height = 0;
		doMotion = false;
		status = MS::kFailure;
    }

    return status;
}

void RibTranslator::portFieldOfView( int port_width, int port_height,
									 double& horizontal,
									 double& vertical,
									 MFnCamera& fnCamera )
//
//  Description:
//      Calculate the port field of view for the camera
//
{
    double left, right, bottom, top;
    double aspect = (double) port_width / port_height;
    computeViewingFrustum(aspect,left,right,bottom,top,fnCamera);

    double neardb = fnCamera.nearClippingPlane();
    horizontal = atan(((right - left) * 0.5) / neardb) * 2.0;
    vertical = atan(((top - bottom) * 0.5) / neardb) * 2.0;
}

void RibTranslator::computeViewingFrustum ( double     window_aspect,
                                            double&    left,
                                            double&    right,
                                            double&    bottom,
                                            double&    top,
		                                    MFnCamera&	cam )
//
//  Description:
//      Calculate the viewing frustrum for the camera
//
{
    double film_aspect = cam.aspectRatio();
    double aperture_x = cam.horizontalFilmAperture();
    double aperture_y = cam.verticalFilmAperture();
    double offset_x = cam.horizontalFilmOffset();
    double offset_y = cam.verticalFilmOffset();
    double focal_to_near = cam.nearClippingPlane() /
						   (cam.focalLength() * MM_TO_INCH);

    focal_to_near *= cam.cameraScale();

    double scale_x = 1.0;
    double scale_y = 1.0;
    double translate_x = 0.0;
    double translate_y = 0.0;

	
	switch (cam.filmFit()) {
    case MFnCamera::kFillFilmFit:
        if (window_aspect < film_aspect) {
            scale_x = window_aspect / film_aspect;
        }
        else {
            scale_y = film_aspect / window_aspect;
        }
 
	    break;
 
	case MFnCamera::kHorizontalFilmFit:
        scale_y = film_aspect / window_aspect;
 
	    if (scale_y > 1.0) {
            translate_y = cam.filmFitOffset() *
                (aperture_y - (aperture_y * scale_y)) / 2.0;
        }
 
	    break;
 
	case MFnCamera::kVerticalFilmFit:
        scale_x = window_aspect / film_aspect;
 

	    if (scale_x > 1.0) {
            translate_x = cam.filmFitOffset() *
                (aperture_x - (aperture_x * scale_x)) / 2.0;
        }
 
	    break;
 
	case MFnCamera::kOverscanFilmFit:
        if (window_aspect < film_aspect) {
            scale_y = film_aspect / window_aspect;
        }
        else {
            scale_x = window_aspect / film_aspect;
        }
 
	    break;

	case MFnCamera::kInvalid:
		break; // handle all cases to avoid compiler warnings
    }
    

    left   = focal_to_near * (-.5*aperture_x*scale_x+offset_x+translate_x);
    right  = focal_to_near * ( .5*aperture_x*scale_x+offset_x+translate_x);
    bottom = focal_to_near * (-.5*aperture_y*scale_y+offset_y+translate_y);
    top    = focal_to_near * ( .5*aperture_y*scale_y+offset_y+translate_y);
}

void RibTranslator::getCameraInfo( MFnCamera& cam )
//
//  Description:
//      Get information about the given camera
//
{
	// Resoultion can change if camera film-gate clips image
	// so we must keep camera width/height separate from render
	// globals width/height.
	//
	cam_width = width;
	cam_height = height;
	
	// If we are using a film-gate then we may need to
	// adjust the resolution to simulate the 'letter-boxed'
	// effect.
	//
	if ( cam.filmFit() == MFnCamera::kHorizontalFilmFit ) {
		if ( !ignoreFilmGate ) {
			int new_height = (int) (cam_width /
				(cam.horizontalFilmAperture() /
				 cam.verticalFilmAperture()));

			if ( new_height < cam_height ) {
				cam_height = new_height;
			}
		}
		
		double hfov, vfov;
		portFieldOfView( cam_width, cam_height, hfov, vfov, cam );
		fov_ratio = hfov/vfov;
	}
	else if ( cam.filmFit() == MFnCamera::kVerticalFilmFit ) {
		int new_width = (int) (cam_height /
			(cam.verticalFilmAperture() /
			 cam.horizontalFilmAperture()));
				
		double hfov, vfov;
	
		// case 1 : film-gate smaller than resolution
		//	        film-gate on
		if ( (new_width < cam_width) && (!ignoreFilmGate) ) {
			cam_width = new_width;
			fov_ratio = 1.0;		
		}
		
		// case 2 : film-gate smaller than resolution
		//	        film-gate off
		else if ( (new_width < cam_width) && (ignoreFilmGate) ) {
			portFieldOfView( new_width, cam_height, hfov, vfov, cam );
			fov_ratio = hfov/vfov;		
		}
		
		// case 3 : film-gate larger than resolution
		//	        film-gate on
		else if ( !ignoreFilmGate ) {
			portFieldOfView( new_width, cam_height, hfov, vfov, cam );
			fov_ratio = hfov/vfov;		
		}
		
		// case 4 : film-gate larger than resolution
		//	        film-gate off
		else if ( ignoreFilmGate ) {
			portFieldOfView( new_width, cam_height, hfov, vfov, cam );
			fov_ratio = hfov/vfov;		
		}
				
	}
	else if ( cam.filmFit() == MFnCamera::kOverscanFilmFit ) {
		int new_height = (int) (cam_width /
			(cam.horizontalFilmAperture() /
			 cam.verticalFilmAperture()));
		int new_width = (int) (cam_height /
			(cam.verticalFilmAperture() /
			 cam.horizontalFilmAperture()));
		
		if ( new_width < cam_width ) {
			if ( !ignoreFilmGate ) {
				cam_width = new_width;
				fov_ratio = 1.0;
			}
			else {
				double hfov, vfov;
				portFieldOfView( new_width, cam_height, hfov, vfov, cam );
				fov_ratio = hfov/vfov;		
			}
		}
		else {
			if ( !ignoreFilmGate )
				cam_height = new_height;
			
			double hfov, vfov;
			portFieldOfView( cam_width, cam_height, hfov, vfov, cam );
			fov_ratio = hfov/vfov;
		}		
	}
	else if ( cam.filmFit() == MFnCamera::kFillFilmFit ) {
		int new_width = (int) (cam_height /
			(cam.verticalFilmAperture() /
		 	 cam.horizontalFilmAperture()));				
		double hfov, vfov;

		if ( new_width >= cam_width ) {
			portFieldOfView( new_width, cam_height, hfov, vfov, cam );
			fov_ratio = hfov/vfov;
		}
		else {						
			portFieldOfView( cam_width, cam_height, hfov, vfov, cam );
			fov_ratio = hfov/vfov;
		}
	}
}

MStatus RibTranslator::ribPrologue()
//
//  Description:
//      Write the prologue for the RIB file
//
{
    MStatus returnStatus = MS::kSuccess;
    MObject cameraNode;

	renderableCameraArray.clear();
	
    // Determine which cameras to render
    //
	MDagPath cameraPath;
    if ( renderAllCameras ) {
    	// Traverse the dag and find renderable cameras
    	//
        MItDag dagIterator( MItDag::kDepthFirst, MFn::kCamera, &returnStatus );
        for (; !dagIterator.isDone(); dagIterator.next())
        {
	    	bool		renderable;
	    	if ( !dagIterator.getPath(cameraPath) )
	    		continue;
	
	    	renderable = false;
	    	MFnCamera fnCameraNode( cameraPath );
	    	fnCameraNode.findPlug( "renderable" ).getValue( renderable );
	
	    	if (renderable)
	    		renderableCameraArray.append(cameraPath);
        }
    } else {
     	// Render the camera for the active view
        //
        M3dView activeView = M3dView::active3dView(); 
        activeView.getCamera( cameraPath );
        renderableCameraArray.append( cameraPath );
    }
	
	// If we didn't find a renderable camera then give up
	if ( renderableCameraArray.length() == 0 ) {
		cerr << "RIB Export: could not find a renderable camera\n";
		return MS::kFailure; // We don't have a renderable camera
	}
	

	// If width or height are 0 then getRenderGlobals
	// could not get this info
	//
	if ( (width == 0) || (height == 0) ) {
		width	= 320;
		height	= 240;
	}
	

    RiFormat( width, height, 1 ); // Output image size
    RiOrientation( RI_RH );       // Right-hand coordinates
    RiSides( 2 );                 // Render two-sided
    RiPixelSamples( (RtFloat)pixelSamples, (RtFloat)pixelSamples );
	 
	ribStatus = kRibBegin;   
	return MS::kSuccess;
}

MStatus RibTranslator::ribEpilogue()
//
//  Description:
//      Write the epilogue for the RIB file
//
{
    if (ribStatus == kRibBegin) ribStatus = kRibOK;
    return (ribStatus == kRibOK ? MS::kSuccess : MS::kFailure);
}

MStatus RibTranslator::scanScene(int frame)
//
//  Description:
//      Scan the DAG at the given frame number and record information
//      about the scene for writing
//
{    
    MTime   mt((double)frame);
    
    if (MGlobal::viewFrame(mt) == MS::kSuccess) {

		MStatus returnStatus;
	
		MItDag dagIterator( MItDag::kDepthFirst, MFn::kInvalid, &returnStatus);

		MDagPath path;
        MObject currentNode;

        // Scan the DAG looking for lights, nurbs surfaces and polygonal
        // meshes
        //
		for (; !dagIterator.isDone(); dagIterator.next()) {
            currentNode = dagIterator.item();
			MFnDagNode	dagNode;
            dagIterator.getPath( path ); 

			if (MS::kSuccess != returnStatus) continue;
			if (!currentNode.hasFn(MFn::kDagNode)) continue;

			returnStatus = dagNode.setObject(currentNode);
			if (MS::kSuccess != returnStatus) continue;


			if (currentNode.hasFn(MFn::kNurbsSurface)
				|| currentNode.hasFn(MFn::kMesh)
				|| currentNode.hasFn(MFn::kLight)) {
	    
				htable->insert(path, frame);
			}
		}


		// Get the camera info for this frame
		//
		MFnCamera   fnCamera(activeCamera);

		// Renderman specifies shutter by time open
		// so we need to convert shutterAngle to time.
		// To do this convert shutterAngle to degrees and
		// divide by 360.
		//
		camera[0].shutter = fnCamera.shutterAngle() * 0.5 / M_PI;

		camera[0].motionBlur = fnCamera.isMotionBlur();
		if ( camera[0].motionBlur ) {
			doCameraMotion = 1;
		}
		else {
			doCameraMotion = 0;
		}

    	int cIndex = ((doCameraMotion && (frame != frameFirst)) ? 1 : 0);

        fnCamera.getPath(path);
        MTransformationMatrix xform( path.inclusiveMatrix() );
        double scale[] = { 1, 1, -1 };
        xform.addScale( scale, MSpace::kTransform );
		camera[cIndex].mat = xform.asMatrixInverse();
            
		if (fnCamera.isClippingPlanes()) {
			camera[cIndex].neardb    = fnCamera.nearClippingPlane();
			camera[cIndex].fardb	   = fnCamera.farClippingPlane();
		} else {
			camera[cIndex].neardb    = 0.001;
			camera[cIndex].fardb	   = 10000.0;
		}
	
		if (camera[cIndex].name != NULL) free(camera[cIndex].name);
		camera[cIndex].name = strdup(fnCamera.name().asChar());
		camera[cIndex].isOrtho = fnCamera.isOrtho();
		
		// The camera's fov may not match the rendered image in Maya
		// if a film-fit is used. 'fov_ratio' is used to account for
		// this.
		//			
		camera[cIndex].hFOV = fnCamera.horizontalFieldOfView()/fov_ratio;
        
        // Determine what information to write out (RGB, alpha, zbuffer)
        //
        camera[cIndex].imageMode.clear();
        bool isOn;
        MPlug boolPlug;
        boolPlug = fnCamera.findPlug( "image" );
		boolPlug.getValue( isOn );
        if ( isOn ) {
            // We are writing RGB info
            //
            camera[cIndex].imageMode = "rgb";
        }
        boolPlug = fnCamera.findPlug( "mask" );
		boolPlug.getValue( isOn );
        if ( isOn ) {
            // We are writing alpha channel info
            //
            camera[cIndex].imageMode += "a";
        }
        boolPlug = fnCamera.findPlug( "depth" );
		boolPlug.getValue( isOn );
        if ( isOn ) {
            // We are writing z-buffer info
            //
            camera[cIndex].imageMode += "z";
        }

		// cerr << "done scanning scene" << endl;
		return MS::kSuccess;
    }
    return MS::kFailure;
}

void RibTranslator::doAttributeBlocking( const MDagPath & newPath, 
                                         const MDagPath & previousPath )
// 
//  Description:
//      This method takes care of the blocking together of objects and
//      their children in the DAG.  This method compares two DAG paths and
//      figures out how many attribute levels to push and/or pop
//
{
 	int newDepth = newPath.length();
    int prevDepth = 0;
    MFnDagNode dagFn( newPath );
    MDagPath npath = newPath;
    MDagPath ppath = previousPath;
    
    if ( previousPath.isValid() ) {
        // Recursive base case
        // If the paths are the same, then we don't have to write
        // any start/end attribute blocks.  So, just return
        //
        if ( newPath == previousPath ) return;
 
        prevDepth = previousPath.length();
 
        // End attribute block if necessary
        //
        if ( newDepth <= prevDepth ) {
            // Write an attribute block end
            //
            RiAttributeEnd();
            attributeDepth--;
            if ( prevDepth > 1 ) {
                ppath.pop();
            }
        }
    }
    if ( ( newDepth >= prevDepth ) && ( newDepth > 1 ) ){
        npath.pop();
    }
    // Recurse and process parents
    //
    if ( ( prevDepth > 1 ) || ( newDepth > 1 ) ) {
        // Recurse
        //
        doAttributeBlocking( npath, ppath );
    }
    
    // Write open for new attribute block if necessary
    //
    if ( newDepth >= prevDepth ) {
        MString name = dagFn.name();
        const char * namePtr = name.asChar();
        RiAttributeBegin();
        RiAttribute( "identifier", "name", &namePtr, RI_NULL );
        if ( newPath.hasFn( MFn::kTransform ) ) {
         	// We have a transform, so write out the info
            //
            RtMatrix ribMatrix;
            MObject transform = newPath.node();
            MFnTransform transFn( transform );
            MTransformationMatrix localTransformMatrix = transFn.transformation();
            MMatrix localMatrix = localTransformMatrix.asMatrix();
            localMatrix.get( ribMatrix );
            RiConcatTransform( ribMatrix );
        }
        attributeDepth++;
    }
}        

MStatus RibTranslator::framePrologue(uint frame)
//  
//  Description:
//  	Write out the frame prologue with all of the geometry that is 
//      global to that frame (only one copy of instanced data is written).
//  
{
    ribStatus = kRibFrame;
	
    RibItHT	iht(htable); 

    while(!iht.isDone()) {
		RibNode *	rn = iht.next();
	
		if ( rn->object()->ignore || rn->object()->type == MRT_Light )
			continue;
	
		if ( rn->object()->written == 0 ) {
            // Write the actual object geometry out into the header
            //
			rn->object()->writeObjectInPrologue();
			rn->object()->written = 1;
		}
		if ( doDef && 
             ( rn->nextFrameObject()->handle() != rn->object()->handle() ) &&
             ( rn->nextFrameObject()->written == 0 ) ) {
			// write second object data for motion blur
			//
			rn->nextFrameObject()->writeObjectInPrologue();
			rn->nextFrameObject()->written = 1;
		}
    }

	MString outFileFmtString;
	outFileFmtString = imageName + "_" + baseFileName;
	
	switch(outFormatControl) {
	case 0:
		outFileFmtString += ".";
		outFileFmtString += outExt;
		break;
	case 1:
		break;
	case 2:
		outFileFmtString += ".";
		outFileFmtString += outFormatString;
		break;
	}
	
	size_t outNameLength;
	outNameLength = outFileFmtString.length();
	
	if (animation || useFrameExt) {
		outFileFmtString += animExt;
		outNameLength += outPadding + 1;
	}
	
	outNameLength++; // Space for the null character
	
	char *outName = (char *)alloca(outNameLength);
	sprintf(outName, outFileFmtString.asChar(), outPadding, frame);

    RiFrameBegin( frame );
    RiDisplay( outName, (RtToken)outFormat.asChar(), 
              (RtToken)camera[0].imageMode.asChar(), RI_NULL );
	RiFormat( cam_width, cam_height, 1 );

    // Camera Settings
    //
    if (camera[0].isOrtho) {
        RiProjection( "orthographic", RI_NULL );
    } else {
        RtFloat fieldOfView = (RtFloat) (camera[0].hFOV * 180.0 / M_PI);
        RiProjection( "perspective", RI_FOV, &fieldOfView, RI_NULL );
    }
	RiClipping( (RtFloat) camera[0].neardb, (RtFloat) camera[0].fardb );

    // Set up for camera motion blur
    //
    doCameraMotion = camera[0].motionBlur && doMotion;
    if ( doMotion ) {
        RiShutter( 0.0F, (RtFloat) camera[0].shutter );
    }
    if ( doCameraMotion && camera[0].mat != camera[1].mat) {
        RiMotionBegin( 2, 0.0F, camera[0].shutter );
    }
    
    RtMatrix cameraMatrix;
    camera[0].mat.get( cameraMatrix );
    RiTransform( cameraMatrix );
    
    if ( doCameraMotion && camera[0].mat != camera[1].mat) {	
    	camera[1].mat.get( cameraMatrix );
    	RiTransform( cameraMatrix );
        RiMotionEnd();
    }
    
    return MS::kSuccess;
}

MStatus RibTranslator::frameEpilogue(uint)
//  
//  Description:
//  	Write out the frame epilogue.
//  
{
    if (doCameraMotion) {
		if (camera[0].name) free(camera[0].name);
		camera[0] = camera[1];
		camera[1].name = NULL;
    }
    if (ribStatus == kRibFrame) {
		ribStatus = kRibBegin;
        RiFrameEnd();
    }
    return (ribStatus == kRibBegin ? MS::kSuccess : MS::kFailure);
}
        
MStatus RibTranslator::frameBody()
//  
//  Description:
//  	Write out the body of the frame.  This includes a dump of the DAG
//  
{
    MStatus returnStatus = MS::kSuccess;
	attributeDepth = 0;
    
    RiWorldBegin();
    
    RibItHT	iht(htable);

    // create lights
    
    int nbLight = 0;
    
    while(!iht.isDone()) {
		RibNode *	rn = iht.next();
	
		if (rn->object()->ignore || rn->object()->type != MRT_Light) continue;
        rn->object()->writeObject();
        rn->object()->written = 1;
		nbLight++;
    }

    if ( nbLight > 0 ) {
        RiSurface( "plastic", RI_NULL );
    }
    
    MMatrix matrix;
    MDagPath path;
    MFnDagNode dagFn;
    MDagPath lastPath;
    
    MItDag dagIt;
    
    // Skip the world node
    //
    dagIt.next();
    
    // Iterate through the DAG and write the objects that we are exporting
    //
    for ( ; !dagIt.isDone(); dagIt.next() ) {
        dagIt.getPath( path );
        dagFn.setObject( path );
        
        if ( !isObjectVisible( path ) ) {
            dagIt.prune();
            continue;
        }
        
        RibNode * ribNode = htable->find( path );
        
        // If this DAG object is in our hash table, then we will output its
        // geometry into the RIB file
        //
        if ( ( NULL == ribNode ) || 
             ( ribNode->object()->ignore ) || 
             ( ribNode->object()->type == MRT_Light ) ) continue;
						
        
        // Do attribute blocking
        //
        doAttributeBlocking( path, lastPath );
        lastPath = path;
        
		// Light-linking: Turn off any lights that are do not
		// affect this object
		//		
		MObject group;
		MObject object;
		MObjectArray ignoredLights;
		group = ribNode->findShadingGroup( ribNode->path() );
		ribNode->getIgnoredLights( group, ignoredLights );
		int last = ignoredLights.length();
		for ( int i=0; i<last; i++ )
		{
			MObject light = ignoredLights[i];			
			if ( NULL != htable ) {
				RibNode * ln = htable->find( light );						
				if ( NULL != ln ) {
					RiIlluminate( ln->object()->lightHandle(), RI_FALSE );
				}
			}				
		}
		
		// If we found a shading group for this object then
		// get the node and see if it matches a renderman
		// shader.
		//
		if ( !group.isNull() ) {
			MObject shader = ribNode->findShader( group );
			switch ( shader.apiType() )
			{
				case MFn::kLambert :
				{
					MFnLambertShader fnShader( shader );
					RiSurface( 	"matte", RI_NULL );
					break;
				}
				case MFn::kBlinn :
				{
					MFnBlinnShader fnShader( shader );
					RiSurface( "plastic", RI_NULL );
					break;
				}
				case MFn::kPhong :
				{
					MFnPhongShader fnShader( shader );
					RiSurface( "plastic", RI_NULL );
					break;
				}
				default:
				{
					break;
				}
			}
		}
        
		// If there is color info, write it
		//
		if ( ribNode->color.r != -1.0 ) {
			RtColor rColor;
			rColor[0] = ribNode->color[0];
			rColor[1] = ribNode->color[1];
			rColor[2] = ribNode->color[2];
			RiColor( rColor );
		}
		
		if ( doMotion && ribNode->matXForm != MRX_Const ) {    
            RiMotionBegin( 2, 0.0F, camera[0].shutter );
	
            // Output the two world matrices for the motionblur
            // This will override the current transformation setting
            //
            path = ribNode->path();
            matrix = ribNode->object()->matrix( path.instanceNumber() );
            RtMatrix ribMatrix;
            matrix.get( ribMatrix );
            RiTransform( ribMatrix );
        	matrix = ribNode->nextFrameObject()->matrix(path.instanceNumber());
            matrix.get( ribMatrix );
            RiTransform( ribMatrix );
            
            RiMotionEnd();
		}
		if (doDef && ribNode->bodyXForm != MRX_Const) {
            RiMotionBegin( 2, 0.0F, camera[0].shutter );
		}        
		if ( ribNode->object()->handle() != NULL ) {
            // The object was written in the prologue, so just reference it
            // 
            ribNode->object()->writeInstance();
		} else {
            // Write the object here directly
            //
			ribNode->object()->writeObject();
		}
		if ( doDef && ( ribNode->bodyXForm != MRX_Const ) ) {
			if ( ribNode->nextFrameObject()->handle() != NULL ) {
                // The object was written in the prologue, so just reference it
                //
            	ribNode->nextFrameObject()->writeInstance();	
            } else {
                // Write the object here directly
                //
				ribNode->nextFrameObject()->writeObject();
			}
            RiMotionEnd();
		}
    }

    // Close off any open attribute blocks
    //    
    while ( attributeDepth > 0 ) {
     	RiAttributeEnd();  
        attributeDepth--;
    }
    
    RiWorldEnd();
    return returnStatus;
}
